package com.example.cloud.demo.lb;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Strings;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.util.StringUtils;
import org.springframework.util.function.SingletonSupplier;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class CustomLoadBalanceRule implements ReactorServiceInstanceLoadBalancer {

    final AtomicInteger position;
    final String serviceId;
    private final SingletonSupplier<ServiceInstanceListSupplier> serviceInstanceListSingletonSupplier;
    private final Map<String, String> metadata;

    public CustomLoadBalanceRule(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, int seedPosition, Map<String, String> metadata) {
        this.metadata = metadata;
        this.position = new AtomicInteger(seedPosition);
        this.serviceId = serviceId;
        this.serviceInstanceListSingletonSupplier = SingletonSupplier.of(() -> serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new));
    }


    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = this.serviceInstanceListSingletonSupplier.obtain();
        return supplier.get(request).next().map((serviceInstances) -> this.processInstanceResponse(supplier, serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }

        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        // 根据客户端条件进行服务提供者实例筛选
        List<ServiceInstance> instanceList = matchedCondition(instances);
        if (instanceList.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: {}", this.serviceId);
            }
            return new EmptyResponse();
        } else if (instanceList.size() == 1) {
            return new EmptyResponse();
        } else {
            int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
            ServiceInstance instance = instanceList.get(pos % instanceList.size());
            return new DefaultResponse(instance);
        }
    }

    private List<ServiceInstance> matchedCondition(List<ServiceInstance> instances) {
        List<ServiceInstance> result = new ArrayList<>();
        for (ServiceInstance instance : instances) {
            Map<String, String> data = instance.getMetadata();
            if (Strings.CS.equals(data.get("env"), this.metadata.get("env"))) {
                result.add(instance);
            }
        }
        return result;
    }


}
